package timing.ukulele.storage.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import timing.ukulele.common.data.ResponseCode;
import timing.ukulele.common.data.ResponseData;
import timing.ukulele.storage.dictionary.FileTypeEnum;
import timing.ukulele.storage.entity.NodeEntity;
import timing.ukulele.storage.repository.NodeRepository;
import timing.ukulele.storage.service.minio.BucketService;
import timing.ukulele.storage.service.minio.ObjectService;
import timing.ukulele.storage.util.UpdateUtil;
import timing.ukulele.storage.vo.NodeTreeVO;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 用户存储对象服务
 */
@Service
@Slf4j
public class NodeService {
    private final NodeRepository repository;
    private final ObjectService objectService;
    private final BucketService bucketService;
    private final MongoOperations mongoOperations;

    public NodeService(NodeRepository nodeRepository, ObjectService objectService, BucketService bucketService, MongoOperations mongoOperations) {
        this.repository = nodeRepository;
        this.objectService = objectService;
        this.bucketService = bucketService;
        this.mongoOperations = mongoOperations;
    }

    public ResponseData<NodeEntity> addFolder(NodeEntity node, String account) {
        ResponseData<NodeEntity> response = new ResponseData<>();
        node.setOwner(account);
        NodeEntity filter = new NodeEntity();
        filter.setType(FileTypeEnum.folder.name());
        filter.setName(node.getName());
        filter.setParentId(node.getParentId());
        filter.setOtherAuthority(null);
        filter.setGroupAuthority(null);
        filter.setUserAuthority(null);
        filter.setSecret(null);
        if (repository.exists(Example.of(filter))) {
            response.setMessage("存在同名文件夹");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        node.setType(FileTypeEnum.folder.name());
        NodeEntity save = repository.save(node);
        response.setMessage("成功");
        response.setData(save);
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    public ResponseData<NodeEntity> modifyFolder(NodeEntity node, String account, String role) {
        ResponseData<NodeEntity> response = new ResponseData<>();
        Optional<NodeEntity> optional = repository.findById(node.getId());
        if (!optional.isPresent()) {
            response.setMessage("不存在该文件夹");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        if (!"ADMIN".equalsIgnoreCase(role)) { //管理员可随意修改
            if (!optional.get().getCreator().equalsIgnoreCase(account)) { // 只能由上传者本人删除
                response.setMessage("您没有权限操作该文件夹");
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        NodeEntity filter = new NodeEntity();
        filter.setType(FileTypeEnum.folder.name());
        filter.setParentId(node.getParentId());
        filter.setName(node.getName());
        List<NodeEntity> exist = repository.findAll(Example.of(filter));
        if (!CollectionUtils.isEmpty(exist)) {
            if (exist.size() > 1 || !exist.get(0).getId().equalsIgnoreCase(node.getId())) {
                response.setMessage("存在同名文件");
                response.setData(null);
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        NodeEntity update = new NodeEntity();
        update.setName(node.getName());
        update.setUserAuthority(node.getUserAuthority());
        update.setGroupAuthority(node.getGroupAuthority());
        update.setOtherAuthority(node.getOtherAuthority());
        try {
            mongoOperations.upsert(new Query(Criteria.where("_id").is(node.getId())), UpdateUtil.getUpdateValues(update), update.getClass());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            response.setMessage("修改失败");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        response.setMessage("成功");
        response.setData(node);
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    public ResponseData<Boolean> removeFolder(String folderId, String account, String role) {
        ResponseData<Boolean> response = new ResponseData<>();
        Optional<NodeEntity> entity = repository.findById(folderId);
        if (!entity.isPresent()) {
            response.setMessage("不存在该文件夹");
            response.setData(Boolean.FALSE);
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        if (!"ADMIN".equalsIgnoreCase(role)) { //管理员可随意删除
            if (!entity.get().getCreator().equalsIgnoreCase(account)) { // 只能由上传者本人删除
                response.setMessage("您没有权限操作该文件夹");
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        NodeEntity filter = new NodeEntity();
        filter.setParentId(folderId);
        filter.setOtherAuthority(null);
        filter.setGroupAuthority(null);
        filter.setUserAuthority(null);
        filter.setSecret(null);
        if (repository.exists(Example.of(filter))) {
            response.setMessage("请先处理文件夹的文件");
            response.setData(Boolean.FALSE);
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        try {
            this.removeFileAndObject(account, entity.get());
        } catch (Exception e) {
            e.printStackTrace();
            response.setMessage("删除失败");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        response.setMessage("成功");
        response.setData(Boolean.TRUE);
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    public ResponseData<NodeEntity> modifyNodePermission(String fileId, Short userAuthority, Short groupAuthority, Short otherAuthority,
                                                         String account,String role) throws IllegalAccessException {
        ResponseData<NodeEntity> response = new ResponseData<>();
        Optional<NodeEntity> optional = repository.findById(fileId);
        if (!optional.isPresent()) {
            response.setMessage("文件不存在");
            response.setData(null);
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        NodeEntity entity = optional.get();
        if (!"ADMIN".equalsIgnoreCase(role)) { //管理员可随意删除
            if (!optional.get().getOwner().equalsIgnoreCase(account)) { // 只能由上传者本人删除
                response.setMessage("您没有权限操作该文件夹");
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        NodeEntity update = new NodeEntity();
        update.setUserAuthority(userAuthority);
        entity.setUserAuthority(userAuthority);
        update.setOtherAuthority(otherAuthority);
        entity.setOtherAuthority(otherAuthority);
        update.setGroupAuthority(groupAuthority);
        entity.setGroupAuthority(groupAuthority);
        update.setParentId(null);
        entity.setSecret(Boolean.FALSE);
        mongoOperations.upsert(new Query(Criteria.where("_id").is(fileId)), UpdateUtil.getUpdateValues(update), update.getClass());

        response.setData(entity);
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    public ResponseData<NodeEntity> addFile(NodeEntity node, String account, String objectName, InputStream stream, String contentType) throws Exception {
        return this.addSecretFile(node, account, objectName, stream, contentType, false);
    }

    @Transactional(rollbackFor = Exception.class)
    public ResponseData<NodeEntity> addSecretFile(NodeEntity node, String account, String objectName, InputStream stream, String contentType, Boolean secret) throws Exception {
        ResponseData<NodeEntity> response = new ResponseData<>();
//        NodeEntity filter = new NodeEntity();
//        filter.setFolder(Boolean.FALSE);
//        filter.setParentId(node.getParentId());
//        if (repository.exists(Example.of(filter))) {
//            response.setMessage("存在同名文件");
//            response.setData(null);
//            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
//            return response;
//        }
        // 先检查是否存在
        if (!bucketService.bucketExists(account))
            bucketService.makeBucket(account);
        node.setOwner(account);
        node.setSize((long) stream.available());
        if (!StringUtils.hasLength(contentType))
            contentType = "application/octet-stream";
        if (secret) {
            // TODO 文件加密
            objectService.putObject(account, objectName, stream, contentType);
        } else {
            node.setSecret(Boolean.FALSE);
            objectService.putObject(account, objectName, stream, contentType);
        }
        node.setType(FileTypeEnum.getFileType(node.getName().substring(node.getName().lastIndexOf(".") + 1)));
        repository.save(node);
        response.setMessage("成功");
        response.setData(node);
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    public ResponseData<NodeEntity> removeFile(String fileId, String account, String role) {
        ResponseData<NodeEntity> response = new ResponseData<>();
        Optional<NodeEntity> entity = repository.findById(fileId);
        if (!entity.isPresent()) {
            response.setMessage("不存在该文件/文件夹");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        if (!"ADMIN".equalsIgnoreCase(role)) { //管理员可随意删除
            if (!entity.get().getCreator().equalsIgnoreCase(account)) { // 只能由上传者本人删除
                response.setMessage("您没有权限操作该文件夹");
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        if (FileTypeEnum.folder.name().equalsIgnoreCase(entity.get().getType())) {
            List<NodeEntity> children = filterParentId(fileId);
            if (!CollectionUtils.isEmpty(children)) {
                response.setMessage("请先处理文件夹下文件");
                response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
                return response;
            }
        }
        try {
            this.removeFileAndObject(entity.get().getOwner(), entity.get());
        } catch (Exception e) {
            e.printStackTrace();
            response.setMessage("删除失败");
            response.setCode(ResponseCode.BUSINESS_ERROR.getCode());
            return response;
        }
        response.setMessage("成功");
        response.setCode(ResponseCode.SUCCESS.getCode());
        return response;
    }

    @Transactional(rollbackFor = Exception.class)
    protected void removeFileAndObject(String bucket, NodeEntity entity) throws Exception {
        // TODO 加入回收站
        if (FileTypeEnum.folder.name().equalsIgnoreCase(entity.getType())) {
            List<NodeEntity> allChildren = this.getAllChildren(entity.getId());
            if (!CollectionUtils.isEmpty(allChildren)) {
                // 翻转一次，从底层往上删除
                Collections.reverse(allChildren);
                for (NodeEntity child : allChildren) {
                    if (!FileTypeEnum.folder.name().equalsIgnoreCase(child.getType())) {
                        objectService.removeObject(bucket, entity.getPath());
                    }
                    repository.deleteById(child.getId());
                }
            }
            repository.deleteById(entity.getId());
        } else {
            objectService.removeObject(bucket, entity.getPath());
            repository.delete(entity);
        }
    }

    public List<NodeEntity> getUserNode(String parentId, String account) {
        NodeEntity filter = new NodeEntity();
        filter.setParentId(parentId);
        filter.setOtherAuthority(null);
        filter.setGroupAuthority(null);
        filter.setUserAuthority(null);
        filter.setSecret(null);
        filter.setOwner(account);
        List<NodeEntity> all = repository.findAll(Example.of(filter));
        return all;
    }

    public List<NodeEntity> getUserNodeByTypes(String account, List<String> types) {
        List<AggregationOperation> list = new ArrayList<>();
        list.add(Aggregation.match(Criteria.where("owner").is(account).and("type").in(types)));
        TypedAggregation<NodeEntity> agg = Aggregation.newAggregation(NodeEntity.class, list);
        AggregationResults<NodeEntity> aggregate = mongoOperations.aggregate(agg, NodeEntity.class, NodeEntity.class);
        return aggregate.getMappedResults();
    }

    private List<NodeEntity> filterParentId(String parentId) {
        NodeEntity filter = new NodeEntity();
        filter.setParentId(parentId);
        filter.setOtherAuthority(null);
        filter.setGroupAuthority(null);
        filter.setUserAuthority(null);
        filter.setSecret(null);
        List<NodeEntity> all = repository.findAll(Example.of(filter));
        return all;
    }

    private List<NodeEntity> getAllChildren(String parentId) {
        List<NodeEntity> list = this.filterParentId(parentId);
        if (!CollectionUtils.isEmpty(list)) {
            list.forEach(item -> {
                //文件夹有下级
                if (FileTypeEnum.folder.name().equalsIgnoreCase(item.getType()))
                    list.addAll(this.getAllChildren(item.getId()));
            });
        }
        return list;
    }

    public NodeTreeVO getNodeTree(String nodeId) {
        Optional<NodeEntity> optional = repository.findById(nodeId);
        if (!optional.isPresent()) {
            return null;
        }
        NodeTreeVO vo = new NodeTreeVO();
        BeanUtils.copyProperties(optional.get(), vo);
        this.getChildren(vo);
        return vo;
    }

    private void getChildren(NodeTreeVO vo) {
        List<NodeEntity> children = filterParentId(vo.getId());
        if (!CollectionUtils.isEmpty(children)) {
            for (NodeEntity child : children) {
                NodeTreeVO childVo = new NodeTreeVO();
                BeanUtils.copyProperties(child, childVo);
                vo.add(childVo);
                if (FileTypeEnum.folder.name().equalsIgnoreCase(child.getType())) {
                    getChildren(childVo);
                }
            }
        }
    }

    /**
     * 压缩文件
     */
    public void zipFile(NodeTreeVO vo, HttpServletResponse response) {
        response.setContentType("application/download");
        response.setCharacterEncoding("UTF-8");
        // 清空response
        response.reset();
        String filename = vo.getName();
        if (CollectionUtils.isEmpty(vo.getNode())) {
            if (!FileTypeEnum.folder.name().equalsIgnoreCase(vo.getType())) {
                try (ServletOutputStream out = response.getOutputStream()) {
                    response.setHeader("Content-disposition",
                            String.format("attachment; filename=\"%s\"", new String(filename.getBytes(StandardCharsets.UTF_8), "ISO8859-1")));
                    InputStream object = objectService.getObject(vo.getOwner(), vo.getPath());
                    IOUtils.copy(object, out);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else { // 空文件
                startZip(response, vo);
            }
        } else {
            startZip(response, vo);
        }
    }

    private void startZip(HttpServletResponse response, NodeTreeVO vo) {
        String filename = vo.getName() + ".zip";
        try (ServletOutputStream out = response.getOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(out)) {
            response.setHeader("Content-disposition",
                    String.format("attachment; filename=\"%s\"", new String(filename.getBytes(StandardCharsets.UTF_8), "ISO8859-1")));
            zipAllFile(true, vo, zipOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void zipAllFile(Boolean root, NodeTreeVO vo, ZipOutputStream zos) {
        for (NodeTreeVO node : vo.getNode()) {
            if (!FileTypeEnum.folder.name().equalsIgnoreCase(node.getType())) {
                try {
                    InputStream object = objectService.getObject(node.getOwner(), node.getPath());
                    try {
                        ZipEntry zipEntry = new ZipEntry(root ? node.getName() : vo.getName() + File.separator + node.getName());
                        zos.putNextEntry(zipEntry);
                        IOUtils.copy(object, zos);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                zipAllFile(false, node, zos);
            }
        }
    }
}
